﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Flight
{
    public class Airplane : Aircraft
    {
        private float vel; // pixels/second
        //private float heading, pitch; // degrees
        private float yaw; // degrees/second
        private float power; // pixels/second/second

        private bool onGround;

        private Sound sound;

        public readonly float LIFT;

        public readonly float AIR_FRICTION;
        public readonly float GROUND_DECELERATION;
        public readonly float BRAKING_DECELERATION;

        public readonly float MAX_GROUND_BACKPITCH; // degrees

        public readonly float MAX_GROUND_YAW_SPEED; // pixels/second

        public readonly float MAX_POWER; // pixels/second/second
        public readonly float POWER_RESPONSE;

        //public readonly float CENTER_Z; // pixels

        public readonly float YAW_DAMPING;

        public readonly float ROLL_SCALE;

        public readonly float GROUND_PITCH_FRICTION;

        public readonly float MAX_ROLL; // degrees
        public readonly float MAX_YAW; // degrees/second
        public readonly float YAW_ACCEL; // degrees/second/second
        public readonly float PITCH_VEL; // degrees/second

        public readonly float STALL_PITCH_DAMPING;
        public readonly float STALL_PITCH_VELOCITY_FACTOR;

        public readonly float STALL_SPEED; // pixels/second
        public readonly float MAX_SPEED; // pixels/second

        public Airplane(Vector3 loc, String name, float heading, float vel, Model model)
            : base(model, 0, 0, new AirplaneAutopilot(), loc, heading, 0)
        {
            Settings settings = new Settings("aircrafts/" + name + "/settings.txt");

            try
            {
                sound = Sound.loop(/*name + "/" + */settings.get<string>("sound"));
            }
            catch (KeyNotFoundException)
            {
            }

            // so that it doesn't warn us about not using these properties
            settings.has("model");
            settings.has("sound");

            this.vel = vel;

            this.yaw = 0;
            this.power = 0;

            this.LIFT = settings.get<float>("lift");

            this.AIR_FRICTION = settings.get<float>("air-friction");
            this.GROUND_DECELERATION = settings.get<float>("ground-deceleration");
            this.BRAKING_DECELERATION = settings.get<float>("braking-deceleration");

            this.GROUND_PITCH_FRICTION = settings.get<float>("ground-pitch-friction");

            this.MAX_GROUND_BACKPITCH = settings.get<float>("max-ground-backpitch");

            this.MAX_GROUND_YAW_SPEED = settings.get<float>("ground-max-yaw-speed");

            this.MAX_POWER = settings.get<float>("max-power");
            this.POWER_RESPONSE = settings.get<float>("power-response");

            this.VIEW_Z = settings.get<float>("view-z");
            this.VIEW_DISTANCE = settings.get<float>("view-distance");
            //this.CENTER_Z = settings.get<float>("center-z");

            this.YAW_DAMPING = settings.get<float>("yaw-damping");

            this.ROLL_SCALE = settings.get<float>("roll-scale");

            this.MAX_ROLL = settings.get<float>("max-roll");
            this.MAX_YAW = settings.get<float>("max-yaw");
            this.YAW_ACCEL = settings.get<float>("yaw-accel");
            this.PITCH_VEL = settings.get<float>("pitch-velocity");

            this.STALL_PITCH_DAMPING = settings.get<float>("stall-damping");
            this.STALL_PITCH_VELOCITY_FACTOR = settings.get<float>("stall-velocity-factor");

            this.STALL_SPEED = settings.get<float>("stall-speed");
            this.MAX_SPEED = settings.get<float>("max-speed");

            settings.listUnusedSettings();
        }
        public Airplane(Vector3 loc, String name, float heading, float vel)
            : this(loc, name, heading, vel, 
                FlightGame.game.getModel(new Settings("aircrafts/" + name + "/settings.txt").get<string>("model")))
        {
        }

        public float getRoll()
        {
            return onGround ? 0 : computeAirRoll(yaw);
        }
        private float computeAirRoll(float yaw)
        {
            if (yaw >= 0)
                return MAX_ROLL * (1 - (float)Math.Exp(-yaw * ROLL_SCALE));
            //else return -computeAirRoll(-yaw);
            else return MAX_ROLL * ((float)Math.Exp(yaw * ROLL_SCALE) - 1);
        }

        public float calcLift()
        {
            return LIFT * vel / MAX_SPEED;
        }

        // yawInput, pitchInput are in range [-1, 1]
        // powerInput is in range [0, 1]
        protected override void no_autopilot_update(float frameTime, ControlInputs inputs)
        {
            float yawInput = inputs.yaw,
                  pitchInput = inputs.pitch,
                  powerInput = inputs.power;
            bool isBraking = inputs.braking;

            if (!onGround)
            {
                // effectiveness of control inputs at different speeds:
                // below stall speed, no input
                // otherwise, input linearly increases with speed
                if (vel <= STALL_SPEED)
                {
                    yawInput = pitchInput = 0;
                }
                else
                {
                    yawInput *= (vel - STALL_SPEED) / (MAX_SPEED - STALL_SPEED);
                }
            }

            pitchInput *= vel / MAX_SPEED;
            //System.out.println((onGround ? "on ground; " : "") + "yaw input = " + (yawInput * 100) + "%");

            this.yaw *= 1 - YAW_DAMPING * frameTime;// * (1 - PApplet.abs(yawInput));

            if (onGround)
            {
                // no yawing above limit
                if (this.vel >= MAX_GROUND_YAW_SPEED)
                    this.yaw = 0;
                // below limit, yaw is linear with speed (decreasing)
                else if (this.vel >= 3)
                    this.yaw = yawInput * MAX_YAW * (MAX_GROUND_YAW_SPEED - vel - 3) / MAX_GROUND_YAW_SPEED;
                else this.yaw = yawInput * MAX_YAW * vel / 3;
            }
            else this.yaw = MathHelper.Clamp(this.yaw + yawInput * YAW_ACCEL * frameTime, -MAX_YAW, MAX_YAW);

            // TODO: add a pitch-velocity value
            this.pitch += pitchInput * PITCH_VEL * frameTime;
            this.heading -= this.yaw * frameTime;

            this.power += (powerInput * MAX_POWER - this.power) * POWER_RESPONSE * frameTime;
            this.vel += this.power * frameTime;

            if (onGround)
            {
                this.vel -= GROUND_DECELERATION * frameTime;
                if (isBraking) this.vel -= BRAKING_DECELERATION * frameTime;
                if (this.vel < 0) this.vel = 0;
            }
            this.vel *= 1 - AIR_FRICTION * frameTime;

            this.heading = mod(this.heading, 360);
            this.pitch = mod(this.pitch, 360);

            if (!onGround && vel <= STALL_SPEED)
            {
                // TODO: fix pitching mechanics/physics
                // aim the pitch straight down
                float pitching = 0;
                if (pitch >= 90)
                    pitching = 270 - pitch;
                else
                    pitching = -(90 + pitch);
                pitching *= 1 - STALL_PITCH_DAMPING;
                pitching *= 1 - STALL_PITCH_VELOCITY_FACTOR * vel / STALL_SPEED;
                this.pitch += pitching * frameTime;

                this.pitch = mod(this.pitch, 360);
            }
            if (onGround)
            {
                if (this.pitch < 180)
                    this.pitch *= 1 - GROUND_PITCH_FRICTION * frameTime;
                else this.pitch -= (this.pitch - 180) * GROUND_PITCH_FRICTION * frameTime;
            }

            Vector3 planeDir = Sphere.degrees(heading, pitch);
            Vector3 up = Sphere.degrees(heading, 90 - pitch);

            Vector3 movement = planeDir * vel;
            movement.Z -= FlightGame.GRAVITY * frameTime;
            movement += up * calcLift();

            if (onGround && vel < STALL_SPEED && movement.Z > 0)
                movement.Z = 0;

            loc += movement * frameTime;

            if (loc.Z < 0)
            {
                onGround = true;

                movement.Z -= loc.Z / frameTime;
                loc.Z = 0;

                if (pitch > 45 && pitch < 360 - 20)
                    Console.WriteLine("crash! p=" + pitch);
                if (pitch > 360 - 20)
                {
                    pitch = 0;
                }
                else if (pitch > MAX_GROUND_BACKPITCH)
                    pitch = MAX_GROUND_BACKPITCH;
            }
            else onGround = false;

            float deltaAlt = movement.Z * frameTime;

            // vnew^2 = vold^2 - 2*a*d
            float inside = this.vel * this.vel - 2 * FlightGame.GRAVITY * deltaAlt;
            if (inside < 0)
            {
                //System.out.println("deltaAlt = " + deltaAlt + ", inside = " + inside);
                inside = 0;
            }
            this.vel = (float)Math.Sqrt(inside);

            if (sound != null)
            {
                float distToCamera = loc.distTo(FlightGame.game.cameraLoc);
                if (this == FlightGame.game.playerAircraft)
                {
                    Console.WriteLine(distToCamera);
                    Console.WriteLine(50 / (distToCamera + 50));
                }
                this.sound.setVolume(50 / (distToCamera + 50));
                //this.sound.setFrequency(this.power / this.MAX_POWER * 2048);
                this.sound.setFrequency(this.power / this.MAX_POWER * 2 - 1);
            }
        }
        private float mod(float n, float d)
        {
            if (n > 0) return n % d;
            else return d + n % d;
        }

        public override void draw()
        {
            //Game.proxy.pushMatrix();
            //{
            //    Game.proxy.translate(loc);
            //    Game.proxy.rotateZ(MathHelper.ToRadians(heading)); // heading
            //    //if (!onGround)
            //    Game.proxy.rotateY(MathHelper.ToRadians(pitch)); // pitch
            //    Game.proxy.rotateX(-MathHelper.ToRadians(getRoll())); // roll
            //    Game.proxy.shape(MODEL);
            //}
            //Game.proxy.popMatrix();
            
            // TODO: roll around the CENTER-Z translated axis
            FlightGame.game.drawModel(MODEL, 1f, heading, pitch, getRoll(), loc);
        }

        public float getVelocity() { return vel; }
        public float getPower() { return power; }
        public float getYaw() { return yaw; }
    }
}
